package cn.iocoder.yudao.module.infra.service.db;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
import cn.iocoder.yudao.module.infra.dal.dataobject.db.InfraDataSourceConfigDO;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.query.SQLQuery;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 数据库表 Service 实现类
 *
 * @author 芋道源码
 */
@Service
public class DatabaseTableServiceImpl implements DatabaseTableService {

    @Resource
    private DataSourceConfigService dataSourceConfigService;

    @Resource
    private ApplicationContext applicationContext;

    @Override
    public List<TableInfo> getTableList(Long dataSourceConfigId, String nameLike, String commentLike) {
        List<TableInfo> tables = getTableList0(dataSourceConfigId, null);
        return tables.stream().filter(tableInfo -> (StrUtil.isEmpty(nameLike) || tableInfo.getName().contains(nameLike))
                        && (StrUtil.isEmpty(commentLike) || tableInfo.getComment().contains(commentLike)))
                .collect(Collectors.toList());
    }

    @Override
    public TableInfo getTable(Long dataSourceConfigId, String name) {
        return CollUtil.getFirst(getTableList0(dataSourceConfigId, name));
    }

    private List<TableInfo> getTableList0(Long dataSourceConfigId, String name) {
        // 获得数据源配置，优先尝试从lowcode模块获取
        InfraDataSourceConfigDO config = getDataSourceConfigWithAdapter(dataSourceConfigId);
        Assert.notNull(config, "数据源({}) 不存在！", dataSourceConfigId);

        // 使用 MyBatis Plus Generator 解析表结构
        DataSourceConfig.Builder dataSourceConfigBuilder = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(),
                config.getPassword());

        // 根据不同数据库类型配置查询类
        configureDatabaseQuery(dataSourceConfigBuilder, config.getUrl());

        StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图，业务上一般用不到
        if (StrUtil.isNotEmpty(name)) {
            strategyConfig.addInclude(name);
        } else {
            // 根据数据库类型配置不同的排除规则
            configureExcludePatterns(strategyConfig, config.getUrl());
        }

        GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 LocalDateTime 类型，不使用 LocalDate
        ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfigBuilder.build(), strategyConfig.build(),
                null, globalConfig, null);

        // 按照名字排序
        List<TableInfo> tables = builder.getTableInfoList();
        tables.sort(Comparator.comparing(TableInfo::getName));
        return tables;
    }

    /**
     * 根据数据库类型配置查询类
     */
    private void configureDatabaseQuery(DataSourceConfig.Builder dataSourceConfigBuilder, String url) {
        String lowerUrl = url.toLowerCase();

        if (JdbcUtils.isSQLServer(url)) {
            // SQL Server jdbc 非标准，参见 https://github.com/baomidou/mybatis-plus/issues/5419
            dataSourceConfigBuilder.databaseQueryClass(SQLQuery.class);
        } else if (lowerUrl.contains("oracle")) {
            // Oracle 数据库配置
            dataSourceConfigBuilder.schema(null); // Oracle 通常使用用户名作为schema
        } else if (lowerUrl.contains("postgresql") || lowerUrl.contains("opengauss")) {
            // PostgreSQL/OpenGauss 配置
            dataSourceConfigBuilder.schema("public"); // 默认使用public schema
        } else if (lowerUrl.contains("dm")) {
            // 达梦数据库配置
            // 达梦数据库通常使用用户名作为schema
        } else if (lowerUrl.contains("kingbase")) {
            // 人大金仓配置
            dataSourceConfigBuilder.schema("public"); // 默认使用public schema
        }
        // MySQL 不需要特殊配置
    }

    /**
     * 根据数据库类型配置排除规则
     */
    private void configureExcludePatterns(StrategyConfig.Builder strategyConfig, String url) {
        String lowerUrl = url.toLowerCase();

        // 通用排除规则：工作流和定时任务前缀的表名
        strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+");

        if (lowerUrl.contains("oracle")) {
            // Oracle 特有的系统表排除规则
            strategyConfig.addExclude("IMPDP_[\\S\\s]+|ALL_[\\S\\s]+|USER_[\\S\\s]+|DBA_[\\S\\s]+");
            strategyConfig.addExclude("V\\$[\\S\\s]+|GV\\$[\\S\\s]+|X\\$[\\S\\s]+");
            strategyConfig.addExclude("SYS_[\\S\\s]+|SYSTEM_[\\S\\s]+|CTXSYS_[\\S\\s]+");
            strategyConfig.addExclude("[\\S\\s]+\\$[\\S\\s]+|[\\S\\s]+\\$"); // 表名不能有 $
        } else if (lowerUrl.contains("postgresql") || lowerUrl.contains("opengauss")) {
            // PostgreSQL/OpenGauss 系统表排除规则
            strategyConfig.addExclude("pg_[\\S\\s]+|information_schema[\\S\\s]*");
            strategyConfig.addExclude("sql_[\\S\\s]+|gp_[\\S\\s]+");
        } else if (lowerUrl.contains("sqlserver")) {
            // SQL Server 系统表排除规则
            strategyConfig.addExclude("sys[\\S\\s]+|msdb[\\S\\s]+|master[\\S\\s]+|model[\\S\\s]+");
            strategyConfig.addExclude("__[\\S\\s]+|MS[\\S\\s]+");
        } else if (lowerUrl.contains("mysql")) {
            // MySQL 系统表排除规则
            strategyConfig.addExclude("information_schema[\\S\\s]*|performance_schema[\\S\\s]*");
            strategyConfig.addExclude("mysql[\\S\\s]*|sys[\\S\\s]*");
        } else if (lowerUrl.contains("dm")) {
            // 达梦数据库系统表排除规则
            strategyConfig.addExclude("SYS[\\S\\s]+|SYSDBA[\\S\\s]+|SYSAUDITOR[\\S\\s]+");
            strategyConfig.addExclude("DM_[\\S\\s]+|V\\$[\\S\\s]+");
        } else if (lowerUrl.contains("kingbase")) {
            // 人大金仓系统表排除规则
            strategyConfig.addExclude("sys_[\\S\\s]+|pg_[\\S\\s]+|information_schema[\\S\\s]*");
        }

        // 通用排除规则：临时表、备份表等
        strategyConfig.addExclude("tmp_[\\S\\s]+|temp_[\\S\\s]+|bak_[\\S\\s]+|backup_[\\S\\s]+");
    }

    /**
     * 获取数据源配置，优先从低代码模块获取，如果不存在再从infra模块获取
     */
    private InfraDataSourceConfigDO getDataSourceConfigWithAdapter(Long dataSourceConfigId) {
        // 优先尝试从低代码模块获取数据源配置（使用反射避免编译时依赖）
        try {
            Object lowcodeAdapter = applicationContext.getBean("lowcodeDataSourceAdapter");
            if (lowcodeAdapter != null) {
                // 通过反射调用 adaptToInfraDataSourceConfig 方法
                java.lang.reflect.Method adaptMethod = lowcodeAdapter.getClass()
                    .getMethod("adaptToInfraDataSourceConfig", Long.class);
                InfraDataSourceConfigDO adaptedConfig = (InfraDataSourceConfigDO) adaptMethod.invoke(lowcodeAdapter, dataSourceConfigId);
                if (adaptedConfig != null) {
                    return adaptedConfig;
                }
            }
        } catch (Exception e) {
            // 忽略异常，说明lowcode模块不存在或未启用，继续使用infra模块
        }

        // 如果低代码模块没有找到，则从infra模块获取
        return dataSourceConfigService.getDataSourceConfig(dataSourceConfigId);
    }

}
